在昨天我們單純使用了 service 就實現出了藍綠部署,那可能就有人會心想:那說好的用 Ingress 實現的方法呢?是不是我隨便講講別人就隨便信信,別急別急你們仔細看看現在不就來了嗎?沒錯!繼昨天的 service 操作後,今天要給各位分享的是使用我們的路由守護神 Ingress 實現金絲雀部署的練習。
Ingress 基於七層的 HTTP 和 HTTPS 協議進行轉發,可以通過域名和路徑對訪問做到更細粒度的劃分。Ingress 作為 Kubernetes 集群中一種獨立的資源,需要通過創建它來製定外部訪問流量的轉發規則,並通過 Ingress Controller 將其分配到一個或多個 Service 中。Ingress Controller 在不同廠商之間有著不同的實現方式,Kubernetes官方維護的 Controller 為 Nginx Ingress ,其支持通過配置註解(Annotations)來實現不同場景下的發布和測試。

目前 Nginx Ingress 提供三種基於 Header、Cookie、權重三種外部流量切分策略,只需要簡單的在註解(Annotations)寫入其提供設定即可使用:
true 的話,將被視為 Canary Ingress ,為以下配置進行流量切分,使兩個 Ingress 互相配合。Ingress 如有與 Header 設定值匹配的請求 Header ,轉導流量到 Canary Ingress 。canary-by-header-value 相同,並且還支持正則表達式配對之,要注意的是當 canary-by-header-value 如果有被設定的話,此註解的功能將會被忽略。Ingress 如有與 Cookie 設定值匹配的請求 Cookie ,轉導流量到 Canary Ingress 。如果將其值設定為 always ,將會轉導所有流量。Canary Ingress 。以上設定的優先級由高到低分別為:canary-by-header -> canary-by-cookie -> canary-weight 。
金絲雀部署與藍綠部署最大的不同是,它不是非黑即白的部署方式,而是介在於黑與白之間,能夠平滑過渡到下一個版本的方法。它能夠緩辦的將修改推送給小部分的使用者,確定沒問題後才正式迭代到下一個版本,以降低值接引入新功能的風險。
以下為金絲雀部署新舊版本更新過程中接收流量的示意圖:

今天我們將會使用 Nginx Ingress 提供的 canary 功能來實現,藉由簡單的在註釋中指定需要被分流的權重比例。
大致實現方法可以簡單分為以下步驟:
v1 版本服務並且使其與 Ingress 綁定成為唯一對外的正式版本。v2 版本完全就緒,此時新舊兩個版本處於同時存在的狀態。Canary Ingress 並設定預期分流到 v2 版本的請求權重。v2 版本有足夠條件取代 v1 版本後,將 Ingress 指向 v2 版本並且關閉 Canary Ingress。v1 版本。大致了解後就馬上來實現吧!
# app-v1.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo-deployment
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v1
  template:
    metadata:
      labels:
        app: my-app
        version: v1
    spec:
      containers:
        - name: foo
          image: mikehsu0618/foo
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: foo-service
spec:
  selector:
    app: my-app
    version: v1
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
# app-v2.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar-deployment
  labels:
    app: my-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-app
      version: v2
  template:
    metadata:
      labels:
        app: my-app
        version: v2
    spec:
      containers:
        - name: bar
          image: mikehsu0618/bar
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: bar-service
spec:
  selector:
    app: my-app
    version: v2
  type: NodePort
  ports:
    - protocol: TCP
      port: 8080
      targetPort: 8080
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: foo-service
      port:
        number: 8080
v1 版本並且啟用 Ingress 服務當作我們的 LoadBalancer :kubectl apply -f app-v1.yaml,ingress.yaml
--------
deployment.apps/foo-deployment created
service/foo-service created
ingress.networking.k8s.io/my-ingress created
查看 v1 版本服務以及 Ingress 狀態:
kubectl get ingress
--------
NAME         CLASS   HOSTS   ADDRESS     PORTS   AGE
my-ingress   nginx   *       localhost   80      2m14s
============================================================================
kubectl get all
--------
NAME                                  READY   STATUS    RESTARTS   AGE
pod/foo-deployment-68df868866-hjsdx   1/1     Running   0          82s
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/foo-service   NodePort    10.109.223.51   <none>        8080:30256/TCP   82s
service/kubernetes    ClusterIP   10.96.0.1       <none>        443/TCP          32d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/foo-deployment   1/1     1            1           82s
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/foo-deployment-68df868866   1         1         1       82s
現在我們可以發送一些請求確認一下是否 v1 版本為唯一對外的服務:
for i in {1..10}; do curl localhost; echo; done
-------
{"data":"Hello foo"}      
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"} 
v2 版本也完整啟動:kubectl apply -f app-v2.yaml
----------
deployment.apps/bar-deployment created
service/bar-service created
查看 v2 版本服務是否啟動完畢:
kubectl get all
----------
NAME                                  READY   STATUS    RESTARTS   AGE
pod/bar-deployment-7bbbff5c97-n7zhj   1/1     Running   0          52s
pod/foo-deployment-68df868866-hjsdx   1/1     Running   0          14m
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/bar-service   NodePort    10.97.149.224   <none>        8080:32756/TCP   52s
service/foo-service   NodePort    10.109.223.51   <none>        8080:30256/TCP   14m
service/kubernetes    ClusterIP   10.96.0.1       <none>        443/TCP          32d
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/bar-deployment   1/1     1            1           52s
deployment.apps/foo-deployment   1/1     1            1           14m
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/bar-deployment-7bbbff5c97   1         1         1       52s
replicaset.apps/foo-deployment-68df868866   1         1         1       14m
確認目前依然是只開放 v1 版本接收請求:
for i in {1..10}; do curl localhost; echo; done
-------
{"data":"Hello foo"}      
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"} 
成功運行~接下來將要迎接主角 Canary Ingress 登場。
Canary Ingress 實現請求依權重分流到新舊版本:# canary-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "10"
  name: canary-ingress
spec:
  ingressClassName: nginx
  defaultBackend:
    service:
      name: bar-service
      port:
        number: 8080
此處我們設定將 10% 比重的請求分流到 bar-service 這個 v2 版本的服務。
建立 Canary Ingress 資源:
kubectl apply -f canary-ingress.yaml
---------
ingress.networking.k8s.io/canary-ingress created
此時我們可以預期對 localhost 的請求中有百分之十會由 v2 版本接收:
for i in {1..10}; do curl localhost; echo; done
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello bar"} // 出現ㄌ!!
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
{"data":"Hello foo"}
太神啦~冰鳥,到此我們已經輕鬆的實現金絲雀部署了。
接下來我們只剩下把等待條件許可時,將 Ingress 設定為新版本的 v2 並且刪除過渡使用的 Canary Ingress 以及 v1 版本資源即可。
我們終於完成了進階部署策略篇,也藉由了部署這個大觀念不斷重複加深了 Deployment Service Pod Ingress 這幫好兄弟的使用方法,再次恭喜堅持走到這個同學還有我自己(嗚嗚,這一切都得來不易。老話一句關於部署的方式完全沒有侷限於任何形式,尤其在工作上我們有更多需要顧慮的 X 因子,各種花式奇淫技巧推層出新為了都是想應付某種特定業務情境,我們唯一能做到的就是穩紮穩打以不變應萬變,把基礎概念打好才是解決問題的根本。話說可以實現第七層負載均衡的 Ingress Controller ,事實上一門非常大的學問,而對 LoadBalancer 更深入了解一定也是學習 Kubernetes 的重要課題,希望日後也能有機會做一個更深入的探討。
千呼萬喚始出來!鐵人賽系列「從異世界歸來發現只剩自己不會 Kubernetes」同名改編作品出版了!
感謝所有交流指教的各路英雄,也感謝願意點閱文章的各位,如果能幫助到任何人都將會是我的榮幸。
本書內容改編自第 14 屆 iThome 鐵人賽 DevOps 組的優選系列文章《從異世界歸來發現只剩自己不會 Kubernetes》。此書是一本綜合性的指南,針對想要探索認識 Kubernetes 的技術人員而生。無論是初涉此領域的新手,還是已有深厚經驗的資深工程師,本書都能提供你所需的知識和技能。
「這本書不僅提供了豐富的範例程式碼和操作指南,讓身為工程師的我們能實際操作來加深認知;更重要的是,它教會我如何從後端工程師的角度去思考和應用 Kubernetes。從容器的生命週期、資源管理到部署管理,每一章都與我們的日常開發工作息息相關。」
──── 雷N │ 後端工程師 / iThome 鐵人賽戰友
天瓏連結: 從異世界歸來發現只剩自己不會 Kubernetes:初心者進入雲端世界的實戰攻略!
相關文章:
相關程式碼同時收錄在:
https://github.com/MikeHsu0618/2022-ithelp/tree/master/Day15
Reference
ContainerSolutions/k8s-deployment-strategies
NGINX Ingress Controller Annotations